package de.herberlin.colormatcher;

/**
 * Finds matching colors for a given RGB.
 * 
 * This code is stolen from 
 * http://www.knorrpage.de/colormatch.html 
 * and ported form javascript to java
 * without really understanding how it works.
 * 
 * @author hans joachim herbertz
 * created 09.07.2003
 */
public abstract class Matcher {

	public static synchronized RGB[] sixColors(RGB source) {
		ud(0,source);
		HS hs = rg2hs(source);
		dom(hs);
		return result;
	}
	/**
	 * Holds  hue, saturation, and brightness
	 * color data.
	 */
	static class HS {
		int h = 0;
		int s = 0;
		int v = 0;
		public String toString() {
			return "h=" + h + " s=" + s + " v=" + v;
		}

	}
	/**
	 * As HS but wiht float values instead of integer.
	 * @author hans joachim herbertz
	 * created 11.07.2003
	 */
	static class HSFloat {
		public float h = 0;
		public float s = 0;
		public float v = 0;
		public HSFloat(HS hs) {
			this.h = hs.h;
			this.s = hs.s;
			this.v = hs.v;
		}
	}
	/**
	 * Holds Red Green Blue colors as float 
	 * @see de.herberlin.colormatcher.RGB
	 * @author hans joachim herbertz
	 * created 11.07.2003
	 */
	static class RGBFloat {
		public float r = 0;
		public float g = 0;
		public float b = 0;

		public RGB getRGB() {
			RGB rgb = new RGB();
			rgb.r = (int) r;
			rgb.g = (int) g;
			rgb.b = (int) b;
			return rgb;
		}
	}
	/**
	 * Makes a RGB out of a HS
	 * @param _hs
	 * @return RGB
	 */
	private static RGB h2r(HS _hs) {
		HSFloat hs = new HSFloat(_hs);
		RGBFloat rg = new RGBFloat();
		if (hs.s == 0) {
			rg.r = rg.g = rg.b = (int) Math.round(((float) hs.v) * 2.55);
			return rg.getRGB();
		}

		hs.s = hs.s / 100;
		hs.v = hs.v / 100;
		hs.h /= 60;
		int i = (int) Math.floor(hs.h);
		float f = hs.h - i;
		float p = hs.v * (1 - hs.s);
		float q = hs.v * (1 - (hs.s * f));
		float t = hs.v * (1 - (hs.s * (1 - f)));

		switch (i) {
			case 0 :
				rg.r = hs.v;
				rg.g = t;
				rg.b = p;
				break;

			case 1 :
				rg.r = q;
				rg.g = hs.v;
				rg.b = p;
				break;

			case 2 :
				rg.r = p;
				rg.g = hs.v;
				rg.b = t;
				break;

			case 3 :
				rg.r = p;
				rg.g = q;
				rg.b = hs.v;
				break;

			case 4 :
				rg.r = t;
				rg.g = p;
				rg.b = hs.v;
				break;

			default :
				rg.r = hs.v;
				rg.g = p;
				rg.b = q;
		}
		rg.r = Math.round(rg.r * 255.0);
		rg.g = Math.round(rg.g * 255.0);
		rg.b = Math.round(rg.b * 255.0);
		return rg.getRGB();
	}

	/**
	 * Seems to be  0<=x<=m
	 * @param x
	 * @param m
	 * @return
	 */
	private static int rc(int x, int m) {
		if (x > m) {
			return m;
		}
		if (x < 0) {
			return 0;
		} else {
			return x;
		}
	}
	/**
	 * The resulting array of RGBs
	 */
	private static RGB[] result=new RGB[6];

	/**
	 * Adds RGBs to the result array
	 * @param pos
	 * @param rgb
	 */	
	private static void ud(int pos, RGB rgb) {
		result[pos]=rgb;
	}

	/**
	 * Calculates matching colors for HS
	 * @param hs
	 */
	private static void dom(HS hs) {
		RGB z = new RGB();
		HS y = new HS();
		HS yx = new HS();
		y.s = hs.s;
		y.h = hs.h;
		if (hs.v > 70) {
			y.v = hs.v - 30;
		} else {
			y.v = hs.v + 30;
		};

		z = h2r(y);
		ud(1, z);
		if ((hs.h >= 0) && (hs.h < 30)) {
			yx.h = y.h = hs.h + 20;
			yx.s = y.s = hs.s;
			y.v = hs.v;

			if (hs.v > 70) {
				yx.v = hs.v - 30;
			} else {
				yx.v = hs.v + 30;
			}
		}

		if ((hs.h >= 30) && (hs.h < 60)) {
			yx.h = y.h = hs.h + 150;
			y.s = rc(hs.s - 30, 100);
			y.v = rc(hs.v - 20, 100);
			yx.s = rc(hs.s - 70, 100);
			yx.v = rc(hs.v + 20, 100);
		}

		if ((hs.h >= 60) && (hs.h < 180)) {
			yx.h = y.h = hs.h - 40;
			y.s = yx.s = hs.s;
			y.v = hs.v;

			if (hs.v > 70) {
				yx.v = hs.v - 30;
			} else {
				yx.v = hs.v + 30;
			}
		}

		if ((hs.h >= 180) && (hs.h < 220)) {
			yx.h = hs.h - 170;
			y.h = hs.h - 160;
			yx.s = y.s = hs.s;
			y.v = hs.v;

			if (hs.v > 70) {
				yx.v = hs.v - 30;
			} else {
				yx.v = hs.v + 30;
			}
		}

		if ((hs.h >= 220) && (hs.h < 300)) {
			yx.h = y.h = hs.h;
			yx.s = y.s = rc(hs.s - 60, 100);
			y.v = hs.v;

			if (hs.v > 70) {
				yx.v = hs.v - 30;
			} else {
				yx.v = hs.v + 30;
			}
		}

		if (hs.h >= 300) {

			if (hs.s > 50) {
				y.s = yx.s = hs.s - 40;
			} else {
				y.s = yx.s = hs.s + 40;
			}

			yx.h = y.h = (hs.h + 20) % 360;
			y.v = hs.v;

			if (hs.v > 70) {
				yx.v = hs.v - 30;
			} else {
				yx.v = hs.v + 30;
			}
		}

		z = h2r(y);
		ud(2, z);
		z = h2r(yx);
		ud(3, z);
		y.h = 0;
		y.s = 0;
		y.v = 100 - hs.v;
		z = h2r(y);
		ud(4, z);
		y.h = 0;
		y.s = 0;
		y.v = hs.v;
		z = h2r(y);
		ud(5, z);
	}
	/**
	 * Converts a RGB to HS
	 * @param rg
	 * @return
	 */
	private static HS rg2hs(RGB rg) {
		HS hs = new HS();
		double m = rg.r;
		if (rg.g < m) {
			m = rg.g;
		}

		if (rg.b < m) {
			m = rg.b;
		}

		double v = rg.r;
		if (rg.g > v) {
			v = rg.g;
		}

		if (rg.b > v) {
			v = rg.b;
		}

		double value = 100.0 * v / 255;
		double delta = v - m;
		if (v == 0.0) {
			hs.s = 0;
		} else {
			hs.s = (int)Math.round(100.0 * delta / v);
		}

		if (hs.s == 0.0) {
			hs.h = 0;
		} else {
			if (rg.r == v) {
				hs.h = (int)Math.round( 60.0 * (rg.g - rg.b) / delta);
			} else if (rg.g == v) {
				hs.h = (int)Math.round (120.0 + 60.0 * (rg.b - rg.r) / delta);
			} else if (rg.b == v) {
				hs.h = (int)Math.round (240.0 + 60.0 * (rg.r - rg.g) / delta);
			}

			if (hs.h < 0.0) {
				hs.h = (int) (hs.h + 360.0);
			}

		}

		hs.v = (int) Math.round(value);
		hs.h = (int) Math.round(hs.h);
		hs.s = (int) Math.round(hs.s);
		return hs;
	}

}
